#include "qwechatimageselector.h"

#include <QtCore/qdir.h>
#include <QtCore/qfileinfo.h>
#include <QtCore/qcache.h>
#include <QtCore/qloggingcategory.h>
#include <QtCore/qfileselector.h>
#include <QtQml/qqmlfile.h>
#include <QtQml/private/qqmlproperty_p.h>
#include <algorithm>

static const int DEFAULT_CACHE = 500;

static inline int cacheSize()
{
    static bool ok = false;
    static const int size =
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
                    qEnvironmentVariableIntValue("QT_QUICK_CONTROLS_IMAGINE_CACHE", &ok);
#else
                    qgetenv("QT_QUICK_CONTROLS_IMAGINE_CACHE").toInt(&ok);
#endif
    return ok ? size : DEFAULT_CACHE;
}

// input: [focused, pressed]
// => [[focused, pressed], [pressed, focused], [focused], [pressed]]
static QList<QStringList> permutations(const QStringList &input, int count = -1)
{
    if (count == -1) {
        count = input.count();
    }

    QList<QStringList> output;
    for (int i = 0; i < input.count(); ++i) {
        QStringList sub = input.mid(i, count);

        if (count > 1) {
            if (i + count > input.count()) {
                sub += input.mid(0, count - i + 1);
            }

            std::sort(sub.begin(), sub.end());
            do {
                if (!sub.isEmpty()) {
                    output += sub;
                }
            } while (std::next_permutation(sub.begin(), sub.end()));
        } else {
            output += sub;
        }

        if (count == input.count()) {
            break;
        }
    }

    if (count > 1) {
        output += permutations(input, --count);
    }

    return output;
}

static QString findFile(const QDir &dir, const QString &baseName,
                        const QString &extensions)
{
    QString filePath = dir.filePath(baseName + QLatin1Char('.') + extensions);
    if (QFile::exists(filePath)) {
        return QFileSelector().select(filePath);
    }
    // return an empty string to indicate that the lookup has been done
    // even if no matching asset was found
    return QLatin1String("");
}

QWeChatImageSelector::QWeChatImageSelector(QObject *parent)
    : QObject(parent),
      m_cache(cacheSize() > 0)
{
}

QUrl QWeChatImageSelector::source() const
{
    return m_source;
}

void QWeChatImageSelector::setSource(const QUrl &source)
{
    if (m_property.isValid()) {
        QQmlPropertyPrivate::write(m_property, source,
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
                                   QQmlPropertyData::BypassInterceptor | QQmlPropertyData::DontRemoveBinding);
#else
                                   QQmlPropertyPrivate::BypassInterceptor |
                                   QQmlPropertyPrivate::DontRemoveBinding);
#endif
    }
    if (m_source == source) {
        return;
    }

    m_source = source;
    emit sourceChanged();
}

void QWeChatImageSelector::setName(const QString &name)
{
    if (m_name == name) {
        return;
    }

    m_name = name;
}

void QWeChatImageSelector::setPath(const QString &path)
{
    if (m_path == path) {
        return;
    }

    m_path = path;
}

void QWeChatImageSelector::setExtensions(const QString &ext)
{
    if (m_ext == ext) {
        return;
    }

    m_ext = ext;
}

QVariantList QWeChatImageSelector::states() const
{
    return m_allStates;
}

void QWeChatImageSelector::setStates(const QVariantList &states)
{
    if (m_allStates == states) {
        return;
    }

    m_allStates = states;
    if (updateActiveStates() && m_complete) {
        updateSource();
    }
}

QString QWeChatImageSelector::separator() const
{
    return m_separator;
}

void QWeChatImageSelector::setSeparator(const QString &separator)
{
    if (m_separator == separator) {
        return;
    }

    m_separator = separator;
    if (m_complete) {
        updateSource();
    }
}

bool QWeChatImageSelector::cache() const
{
    return m_cache;
}

void QWeChatImageSelector::setCache(bool cache)
{
    m_cache = cache;
}

void QWeChatImageSelector::write(const QVariant &value)
{
    setUrl(value.toUrl());
}

void QWeChatImageSelector::setTarget(const QQmlProperty &property)
{
    m_property = property;
}

void QWeChatImageSelector::classBegin()
{
}

void QWeChatImageSelector::componentComplete()
{
    setUrl(m_property.read().toUrl());
    m_complete = true;
    updateSource();
}

QString QWeChatImageSelector::cacheKey() const
{
    if (!m_cache) {
        return QString();
    }

    return m_path + m_name + m_activeStates.join(m_separator);
}

void QWeChatImageSelector::updateSource()
{
    if(m_url.isEmpty()) {
        setSource(m_url);
        return;
    }

    static QCache<QString, QString> cache(cacheSize());

    const QString key = cacheKey();

    QString bestFilePath;

    if (m_cache) {
        QString *cachedPath = cache.object(key);
        if (cachedPath) {
            bestFilePath = *cachedPath;
        }
    }

    // note: a cached file path may be empty
    if (bestFilePath.isNull()) {
        QDir dir(m_path);
        int bestScore = -1;

        const QList<QStringList> statePerms = permutations(m_activeStates);
        for (const QStringList &perm : statePerms) {
            const QString filePath = findFile(dir,
                                              m_name + m_separator + perm.join(m_separator), m_ext);
            if (!filePath.isEmpty()) {
                int score = calculateScore(perm);
                if (score > bestScore) {
                    bestScore = score;
                    bestFilePath = filePath;
                }
            }
        }

        if (bestFilePath.isEmpty()) {
            bestFilePath = findFile(dir, m_name, m_ext);
        }

        if (m_cache) {
            cache.insert(key, new QString(bestFilePath));
        }
    }

    if (bestFilePath.startsWith(QLatin1Char(':'))) {
        setSource(QUrl(QLatin1String("qrc") + bestFilePath));
    } else {
        setSource(QUrl::fromLocalFile(bestFilePath));
    }
}

QUrl QWeChatImageSelector::url() const
{
    return m_url;
}

void QWeChatImageSelector::setUrl(const QUrl &url)
{
    if (url == m_url) {
        return;
    }

    m_url = url;

    QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(url));
    setName(fileInfo.baseName());
    setPath(fileInfo.path());
    setExtensions(fileInfo.completeSuffix());

    if (m_complete) {
        updateSource();
    }
}

bool QWeChatImageSelector::updateActiveStates()
{
    QStringList active;

#if (QT_VERSION >= QT_VERSION_CHECK(5, 7, 0))
    for (const QVariant &v : qAsConst(m_allStates)) {
#else
    for (const QVariant &v : const_cast<QVariantList &>(m_allStates)) {
#endif
        const QVariantMap state = v.toMap();
        if (state.isEmpty()) {
            continue;
        }
        auto it = state.begin();
        if (it.value().toBool()) {
            active += it.key();
        }
    }

    if (m_activeStates == active) {
        return false;
    }

    m_activeStates = active;
    return true;
}

int QWeChatImageSelector::calculateScore(const QStringList &states) const
{
    int score = 0;
    for (int i = 0; i < states.count(); ++i) {
        score += (m_activeStates.count() - m_activeStates.indexOf(states.at(i))) << 1;
    }
    return score;
}
